package momo.app.multicast.presence;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import momo.app.multicast.MsgRcvListener;
import momo.app.multicast.MulticastComm;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * This class is the equivalent of P2PNode for presence.
 * When presence is used this class should be used instead of P2PNode.
 * @author Sally Ang
 *
 */
public class PresenceMulticastComm extends MulticastComm {
	private Log log = LogFactory.getLog(PresenceMulticastComm.class);
	
    private Presence presence;
    private String user_name;
    private MsgRcvListenerImpl msg_rcv_listener_impl;
    private PresenceCallback presence_callback;
    private NetworkState network_state;
    private boolean ackReceived;
    
    private Object ackLock = new Object();
    
    public PresenceMulticastComm(PresenceCallback presence_callback) {
        super();
        this.network_state = NetworkState.OUT;
        this.presence_callback = presence_callback;
        this.msg_rcv_listener_impl = new MsgRcvListenerImpl(this);
        ackReceived = false;
        super.addMsgRcvListener(this.msg_rcv_listener_impl);
    }
    
    public PresenceMulticastComm(int commPort, int statusPort, 
    		PresenceCallback presence_callback) {
    	super(commPort, statusPort);
    	this.network_state = NetworkState.OUT;
        this.presence_callback = presence_callback;
        this.msg_rcv_listener_impl = new MsgRcvListenerImpl(this);
        super.addMsgRcvListener(this.msg_rcv_listener_impl);
    }
    
    public PresenceMulticastComm(String id, int commPort, int statusPort, 
    		PresenceCallback presence_callback) {
    	super(id, commPort, statusPort);
    	this.network_state = NetworkState.OUT;
    	this.user_name = id;
        this.presence_callback = presence_callback;
        this.msg_rcv_listener_impl = new MsgRcvListenerImpl(this);
        super.addMsgRcvListener(this.msg_rcv_listener_impl);
    }

    public Presence getPresence() {
        return presence;
    }

    public void setPresence(Presence presence) {
        this.presence = presence;
    }
    
    public void removePresence() {
        presence.finalize();
        presence = null;
    }
    
    /**
     * Send presence message with type Message.TYPE_PRESENCE
     * @param topic
     * @param message_content
     */
    public void publishPresenceMessage(PresenceMessage message_content) {
    	if (presence == null)
    		return;
        Message message = new Message(Message.TYPE_PRESENCE, message_content);
        System.out.println("before synchsend");
        super.synchronizedSend(message.marshall());
        System.out.println("after synchsend");
    }
    
    public void publishAcknowledgement() {
    	log.warn("publish acknowledgement 1");
    	this.setAcknowledgementReceived(true);
    	Message message = new Message(Message.TYPE_ACKNOWLEDGEMENT, null);
    	super.synchronizedSend(message.marshall());
    }
    
    public void send(String message_content) {
        Message message = new Message(Message.TYPE_NORMAL, message_content);
        super.send(message.marshall());
    }
    
    public void synchronizedSend(String message_content) {
        Message message = new Message(Message.TYPE_NORMAL, message_content);
        super.synchronizedSend(message.marshall());
    }
    
    /**
     * Remove presence after unsubscribe from topic
     */
    public void leave() {
        super.leave();
        setNetworkState(NetworkState.OUT);
        if (presence != null)
        	removePresence();
    }
    
    /**
     * Send message with type Message.TYPE_NEW_PEER_JOIN to tell other user
     * that a new peer come
     * @param topic
     * @param user_name
     * @throws IOException 
     */
    public void connect(String host, int statusPort) throws IOException {
    	if (presence != null)
    		this.presence.finalize();
    	System.out.println(statusPort);
    	if (statusPort == 0) {
    		setNetworkState(NetworkState.CONNECTED);
    	} else {
    		setNetworkState(NetworkState.DELAYED);
    	}
    	super.connect(host, statusPort);
    	Presence presence = new Presence(this, presence_callback);
        setPresence(presence);
        try {
    	   Thread.sleep(1500);
        } catch (InterruptedException e) {
        	e.printStackTrace();
        }
        if (getNetworkState().equals(NetworkState.DELAYED)) {
        	Message message = new Message(Message.TYPE_NEW_PEER_JOIN, this.user_name);
        	System.out.println("send new peer join");
        	super.synchronizedSend(message.marshall());
        } else {
        	presence.initiatePresence();
        }
    }
    
    public void addMsgRcvListener(MsgRcvListener newListener) {
    	this.msg_rcv_listener_impl.setMsgRcvListener(newListener);
    }
    
    public NetworkState getNetworkState() {
    	synchronized(this.network_state) {
    		return this.network_state;
    	}
    }
    
    public void setNetworkState(NetworkState network_state) {
    	synchronized(this.network_state) {
    		NetworkState old_state = this.network_state;
    		this.network_state = network_state;
    		log.warn("network state: " + this.network_state.toString());
    		if (!old_state.equals(NetworkState.CONNECTED) && 
    				this.network_state.equals(NetworkState.CONNECTED)) {
    			System.out.println("on connected called");
    			this.presence_callback.onConnected();
    		}
    	}
    }

	public void onReceiveAcknowledgement() {
		log.warn("receive ack");
		if (getNetworkState().equals(NetworkState.DELAYED)) {
			setNetworkState(NetworkState.ACKNOWLEDGED);
		} else if (getNetworkState().equals(NetworkState.CONNECTED)) {
			boolean needCancelPresence = false;
			synchronized(this.ackLock) {
				if (!getAcknowledgementReceived()) {
					setAcknowledgementReceived(true);
					needCancelPresence = true;
				}
			}
			if (needCancelPresence)			
				presence.cancelPresence();
		}
	}
	
	//synchronized
	public  void setAcknowledgementReceived(boolean value) { //boolean acknowledgement_received) {
		log.warn("set ack received");
		synchronized(this.ackLock) {
			this.ackReceived = value;
		}
	}
	public boolean getAcknowledgementReceived() {
		synchronized(this.ackLock) {
			return ackReceived;
		}
	}
}
